library(tidyverse)
## Warning: package 'ggplot2' was built under R version 4.5.2
## Warning: package 'purrr' was built under R version 4.5.2
## Warning: package 'stringr' was built under R version 4.5.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.1     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.2.0     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(caret)
## Warning: package 'caret' was built under R version 4.5.2
## Cargando paquete requerido: lattice
## 
## Adjuntando el paquete: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(factoextra)
## Warning: package 'factoextra' was built under R version 4.5.2
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(plotly)
## 
## Adjuntando el paquete: 'plotly'
## 
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## 
## The following object is masked from 'package:stats':
## 
##     filter
## 
## The following object is masked from 'package:graphics':
## 
##     layout
set.seed(123)
gene_expression <- read.table("gene_expression.csv", sep = ";", header = FALSE)
column_names <- read.table("column_names.txt", header = FALSE, stringsAsFactors = FALSE)
classes <- read.table("classes.csv", sep = ";", header = FALSE, stringsAsFactors = FALSE)
colnames(classes) <- c("SampleID", "Class")


colnames(gene_expression) <- column_names$V1
rownames(gene_expression) <- classes$SampleID

# Separamos X e Y desde el inicio para trabajar más limpio
X <- gene_expression
y <- as.factor(classes$Class)

# --- 3. NORMALIZACIÓN (CENTRADO Y ESCALADO) ---
# Al no haber NAs, pasamos directamente de la carga a la escala
preproc_scale <- preProcess(X, method = c("center", "scale"))
## Warning in preProcess.default(X, method = c("center", "scale")): These
## variables have zero variances: MIER3, ZCCHC12, RPL22L1
X_scaled <- predict(preproc_scale, X)

# --- 4. DATASET FINAL ---
# Este es el objeto que usarás para tus algoritmos supervisados
data_final <- X_scaled
data_final$Class <- y
gene_expression <- read.table("gene_expression.csv",
                              sep = ";",
                              header = FALSE)

##carga de nombres de genes
column_names <- read.table("column_names.txt",
                           header = FALSE,
                           stringsAsFactors = FALSE)
##cargar las clases
classes <- read.table("classes.csv",
                      sep = ";",
                      header = FALSE,
                      stringsAsFactors = FALSE)
colnames(classes) <- c("SampleID", "Class")


#CONSTRUIR EL DATAFRAME FINAL

##Asignar nombres de genes como nombres de columnas
colnames(gene_expression) <- column_names$V1
##Asignar IDs de muestra como nombres de filas
rownames(gene_expression) <- classes$SampleID
##Añadir la clase como última columna
data <- gene_expression %>%
  mutate(Class = as.factor(classes[,2]))


#DEPURADO DE DATOS
##calcular la proporcion de NA por gen
na_prop <- colMeans(is.na(data))
##eliminar genes con mas del 20% de NA
data <- data[, na_prop < 0.2] #(no se elimina ningun gen)


##No haria falta ya que no hay ningun NA, pero en un caso real cabria hacerlo

##separar datos y clase ya que nunca imputamos la clase, solo los datos
x <- data[, colnames(data) != "Class"]
y <- data$Class




#NORMALIZACION
##centrar y escalar
preproc_scale <- preProcess(x, method = c("center", "scale"))
## Warning in preProcess.default(x, method = c("center", "scale")): These
## variables have zero variances: MIER3, ZCCHC12, RPL22L1
x_scaled <- predict(preproc_scale, x)

##dataset final
data_final <- x_scaled
data_final$Class <- y

PCA

Tanto para PCA como K-MEANS se se eliminan las columnas con varianza 0 porque son variables constantes que no aportan ninguna información útil. Dado que PCA busca las direcciones de mayor variabilidad y K-means calcula distancias para agrupar puntos, una columna que siempre tiene el mismo valor no ayuda a distinguir a los individuos ni a identificar patrones, actuando solo como ruido innecesario. Además, mantener estas columnas causaría errores matemáticos (división por cero) durante el escalado de los datos, ya que estas técnicas necesitan que las variables presenten algún grado de variación para poder comparar sus magnitudes de forma correcta.

# Columnas con SD = 0 que indican que son constantes

desviaciones <- apply(X_scaled, 2, sd) # La función apply con margen 2 calcula la SD por columna.


columnas_a_mantener <- desviaciones > 0 # se mantienen las columnas que su desviación estándar es > 0
X_scaled_sd <- X_scaled # se crea una copia para no modificar el original
data.filtrada2 <- X_scaled_sd[, columnas_a_mantener]

genes_eliminados <- sum(!columnas_a_mantener)
nombres_genes_eliminados <- colnames(X_scaled_sd)[!columnas_a_mantener]



cat("Genes (columnas) eliminados (SD = 0):", genes_eliminados, "\n")
## Genes (columnas) eliminados (SD = 0): 3
cat("Genes:",paste(nombres_genes_eliminados, collapse = ", "), "\n")
## Genes: MIER3, ZCCHC12, RPL22L1
# filtro sobre el dataset original para quedarte solo con los genes útiles
x_filtrado_sd <- X[, columnas_a_mantener]

#  escalado y centrado sobre el dataset filtrado
preproc_scale_sd <- preProcess(x_filtrado_sd, method = c("center", "scale"))
X_scaled_sd <- predict(preproc_scale_sd, x_filtrado_sd)
# Cálculo de componentes principales (usamos los datos ya escalados)
pca.results <- prcomp(X_scaled_sd, center = FALSE, scale. = FALSE)

# Resultado de las componentes principales en un dataframe
pca.df <- data.frame(pca.results$x)

# Análisis de la Varianza
varianzas <- pca.results$sdev^2
total.varianza <- sum(varianzas)
varianza.explicada <- varianzas / total.varianza
varianza.acumulada <- cumsum(varianza.explicada)

# Número de componentes que explican el 70%
n.pc <- min(which(varianza.acumulada > 0.70))
print(paste("Número de PCs para el 70% de varianza:", n.pc))
## [1] "Número de PCs para el 70% de varianza: 44"

Grafico PCA 2D

# Configuración de etiquetas dinámicas para los ejes
x_label <- paste0("PC1 (", round(varianza.explicada[1] * 100, 2), "%)")
y_label <- paste0("PC2 (", round(varianza.explicada[2] * 100, 2), "%)")

# Representación gráfica
# Usamos 'y' que definimos antes como el factor de las clases
ggplot(pca.df, aes(x = PC1, y = PC2, color = y)) + 
  geom_point(size = 3, alpha = 0.8) +
  scale_color_manual(values = c('red', 'blue', 'green', 'orange', 'purple')) +
  labs(title = 'PCA', 
      
       x = x_label, 
       y = y_label, 
       color = 'Grupo') +
  theme_classic() +
  theme(
    panel.grid.major = element_line(color = "gray90"), 
    panel.grid.minor = element_blank(),
    panel.background = element_rect(fill = "gray95"), 
    plot.title = element_text(hjust = 0.5, face = "bold"),
    plot.subtitle = element_text(hjust = 0.5)
  )

Gráfico PCA 3D

library(plotly)

# Definir la etiqueta del tercer eje (PC3)
z_label <- paste0('PC3 (', round(varianza.explicada[3]*100, 2), '%)')

#  Generar el gráfico interactivo 3D
plot_ly(pca.df, 
        x = ~PC1, 
        y = ~PC2, 
        z = ~PC3, 
        color = ~y,  # Usamos 'y' que contiene tus etiquetas de Class
        colors = c('red', 'blue', 'green', 'orange', 'purple'),
        type = 'scatter3d', 
        mode = 'markers',
        marker = list(size = 4, opacity = 0.8)) %>%
  layout(title = 'PCA 3D - Types of Cancer',
         scene = list(xaxis = list(title = x_label),
                      yaxis = list(title = y_label),
                      zaxis = list(title = z_label)))

K-means

library(factoextra)
# n optimo de clusters
fviz_nbclust(X_scaled, kmeans, method = "wss") +
  ggtitle("Optimal number of clusters", subtitle = "") +
  theme_classic()

A partir de \(k = 4\), la curva se vuelve mucho más plana (lineal), lo que sugiere que añadir más grupos no está reduciendo la varianza interna de forma significativa.

k-means 2D

# 1. Ejecutar K-means sobre los datos ESCALADOS
# Usamos k=5 como indicaste en tu ejemplo
set.seed(123) # Para que el resultado sea reproducible
kmeans.result <- kmeans(X_scaled_sd, 
                        centers = 4, 
                        iter.max = 100, 
                        nstart = 25)

# 2. Visualización con fviz_cluster
# Esta función hace un PCA interno para poder mostrar los datos en 2D
fviz_cluster(kmeans.result, 
             data = X_scaled_sd, 
             geom = "point",
             palette = c("#E41A1C", "#377EB8", "#4DAF4A", "#FF7F00"), 
             ellipse.type = "convex", # Encierra los grupos en polígonos
             ggtheme = theme_minimal(),
             main = "Clustering K-means (k=4)")

K-means sobre PCA

fviz_nbclust(pca.df[,1:3], kmeans, method = "wss") +
  ggtitle("Optimal number of clusters", subtitle = "") +
  theme_classic()

Similar al anterior, se seleccionan 4 componentes.

# 1. Ejecutar K-means sobre las primeras 3 dimensiones del PCA
# Esto agrupa las muestras según su posición en el espacio 3D que acabas de visualizar

kmeans.pca <- kmeans(pca.df[, 1:3], 
                     centers = 4, 
                     nstart = 100, 
                     iter.max = 300)


# 2. Visualización 2D (Proyección de los clusters)
fviz_cluster(kmeans.pca, 
             data = pca.df[, 1:3], 
             geom = "point", 
             palette = c("red", "blue", "green", "orange"),
             ellipse.type = "convex",
             ggtheme = theme_minimal(),
             main = "K-means Clustering sobre PC1-PC3")

# 3. Preparar los clusters para el gráfico 3D
clusters <- kmeans.pca$cluster
# 4. Visualización 3D Interactiva
plot_ly(
  x = pca.df[, 1],
  y = pca.df[, 2],
  z = pca.df[, 3],
  color = as.factor(clusters),
  colors = c("red", "blue", "green", "orange"),
  type = "scatter3d",
  mode = "markers",
  marker = list(size = 4, opacity = 0.8)
) %>%
  layout(
    title = 'Clusters K-means en Espacio PCA (3D)',
    scene = list(
      xaxis = list(title = 'PC1'),
      yaxis = list(title = 'PC2'),
      zaxis = list(title = 'PC3')
    )
  )

1. ¿Por qué se seleccionaron estas técnicas de reducción de dimensionalidad? (PCA)

Se eligió PCA debido a la alta complejidad y dimensionalidad del dataset original (como demuestra el hecho de necesitar 44 componentes para alcanzar el 70% de la varianza). El objetivo principal era simplificar la estructura de los datos sin perder la información esencial, permitiendo identificar patrones globales y tendencias generales que a simple vista son invisibles. Además, se utilizó como paso previo para mejorar la eficiencia de otros algoritmos, eliminando el ruido y la redundancia entre variables correlacionadas.

2. ¿Por qué se seleccionaron estas técnicas de clusterización? (K-means)

La elección de K-means responde a su equilibrio entre eficacia y coste computacional. Es una técnica ideal para establecer una base de segmentación rápida, especialmente útil cuando se trabaja tanto sobre el dataset completo como sobre las dimensiones proyectadas por el PCA. Se seleccionó por su capacidad para realizar una partición directa del espacio, facilitando la interpretación de los grupos formados en función de su cercanía a los centroides.

3. Aspectos positivos y negativos de cada una

  • PCA:

  • Positivo: Excelente para comprimir información y facilitar la visualización de grandes volúmenes de datos. Ayuda a evitar el overfitting al reducir el número de variables.

  • Negativo: Tiene una naturaleza lineal, lo que significa que si existen relaciones complejas o curvas en los datos (no lineales), el PCA no podrá capturarlas adecuadamente.

  • K-means:

  • Positivo: Es extremadamente rápido y escalable. Su implementación es intuitiva y permite asignar etiquetas de grupo de manera inmediata a cada observación.

  • Negativo: Es muy vulnerable a los valores atípicos y obliga a definir de antemano el número de clústeres , lo cual no siempre es evidente. Además, asume que los grupos tienen formas esféricas, lo que no siempre ocurre en la realidad.

4. En la clusterización, ¿podéis afirmar con certeza que los clústeres generados son los mejores posibles?

No se puede afirmar con certeza absoluta. La clusterización es un proceso de aprendizaje no supervisado y el “mejor” clúster suele ser subjetivo o dependiente de la métrica utilizada.

  • Motivos: En nuestro análisis, observamos un solapamiento significativo entre varios grupos (como CFB, CGC, CHC y HPB), lo que indica que las fronteras entre ellos no están definidas con nitidez.
  • Dependencia inicial: K-means puede converger en un “mínimo local” dependiendo de dónde se coloquen los centroides al inicio.
  • Conclusión: Aunque los clústeres son útiles para identificar tendencias, la alta dimensionalidad del problema sugiere que podrían existir agrupaciones más óptimas utilizando métodos que detecten formas no esféricas o relaciones no lineales.